#include "fXmlParser.h"

void XMLStringTrim( TPtr aPtr )
{
    /*TInt len = aPtr.Length( );
    
     for( TInt i = 0; i < len; i++ )
     {
     if( ( aPtr[i] == KNewline ) || ( aPtr[i] == KCarriageReturn ) )
     {
     aPtr[i] = KWhitespace;
     }
     }*/
    aPtr.Trim( );
}

/////////////////////////////////////////////////
// class CAttribute
/////////////////////////////////////////////////

CXmlAttribute *CXmlAttribute::NewL( const TDesC & aName, const TDesC & aNamespace, const TDesC & aValue )
{
    CXmlAttribute *attr = new ( ELeave ) CXmlAttribute( );
    
    CleanupStack::PushL( attr );
    attr->ConstructL( aName, aNamespace, aValue );
    CleanupStack::Pop( ); // attr

    return attr;
}

CXmlAttribute::CXmlAttribute( )
{
}

CXmlAttribute::~CXmlAttribute( )
{
    delete iName;
    delete iNamespace;
    delete iValue;
}

void CXmlAttribute::ConstructL( const TDesC & aName, const TDesC & aNamespace, const TDesC & aValue )
{
    iName = aName.AllocL( );
    iNamespace = aNamespace.AllocL( );
    iValue = aValue.AllocL( );
    
    ::XMLStringTrim( iValue->Des( ) );
}

const TDesC & CXmlAttribute::GetName( ) const
{
    return *iName;
}

const TDesC & CXmlAttribute::GetNamespace( ) const
{
    return *iNamespace;
}

const TDesC & CXmlAttribute::GetValue( ) const
{
    return *iValue;
}

/////////////////////////////////////////////////
// class CEAXPParser
/////////////////////////////////////////////////

fXmlParser *fXmlParser::NewLC( MSAXEventListener & aListener )
{
    fXmlParser *parser = new ( ELeave ) fXmlParser( aListener );
    
    CleanupStack::PushL( parser );
    parser->ConstructL( );
    
    return parser;
}

fXmlParser *fXmlParser::NewL( MSAXEventListener & aListener )
{
    fXmlParser *parser = fXmlParser::NewLC( aListener );
    
    CleanupStack::Pop( ); // parser

    return parser;
}

fXmlParser::fXmlParser( MSAXEventListener & aListener ) :
    iListener( aListener ), iCoreState( ECoreNullState )
{
}

fXmlParser::~fXmlParser( )
{
    // dealloc buffers
    delete iAttrName;
    delete iAttrValue;
    delete iFreeText;
    delete iUnescapeBuffer;
    
    delete[ ] iEscapedCharMap;
}

void fXmlParser::ConstructL( )
{
    // construct default size buffers
    AllocateDefaultSizeBuffersL( );
    
    // construct the escaped character mapping tables
    iNumEscapeMappings = 0;
    iEscapeMapCapacity = 10;
    iEscapedCharMap = new ( ELeave ) TEscapedCharMapping[ iEscapeMapCapacity ];
    AddEscapeMappingL( _L("amp"), '&' );
    AddEscapeMappingL( _L("lt"), '<' );
    AddEscapeMappingL( _L("gt"), '>' );
    AddEscapeMappingL( _L("quot"), '\"' );
    AddEscapeMappingL( _L("apos"), '\'' );
    AddEscapeMappingL( _L("nbsp"), ' ' );
}

void fXmlParser::AllocateDefaultSizeBuffersL( )
{
    delete iAttrName;
    
    iAttrName = NULL;
    iAttrName = HBufC::NewL( 32 );
    delete iAttrValue;
    
    iAttrValue = NULL;
    iAttrValue = HBufC::NewL( 32 );
    delete iFreeText;
    
    iFreeText = NULL;
    iFreeText = HBufC::NewL( 128 );
    delete iUnescapeBuffer;
    
    iUnescapeBuffer = NULL;
    iUnescapeBuffer = HBufC::NewL( 128 );
}

void fXmlParser::AddEscapeMappingL( const TDesC & aEscape, TChar aChar )
{
    if( iNumEscapeMappings == iEscapeMapCapacity )
    {
        iEscapeMapCapacity *= 2;
        delete[ ] iEscapedCharMap;
        iEscapedCharMap = NULL;
        iEscapedCharMap = new ( ELeave ) TEscapedCharMapping[ iEscapeMapCapacity ];
    }

    TEscapedCharMapping *mapping = iEscapedCharMap + iNumEscapeMappings;
    
    iNumEscapeMappings++;
    mapping->iEscape.Copy( aEscape );
    mapping->iChar = aChar;
}

void fXmlParser::ValidateSizeAndCopyL( HBufC ** aTo, const TDesC & aFrom )
{
    // check if need to realloc
    if( ( *aTo )->Des( ).MaxLength( ) < aFrom.Length( ) )
    {
        delete *aTo;
        
        *aTo = NULL;
        *aTo = aFrom.AllocL( );
    }
    else
    {
        // buffer large enough; just copy
        ( *aTo )->Des( ).Copy( aFrom );
    }
}

void fXmlParser::ValidateSizeL( HBufC ** aBuf, TInt aSize )
{
    if( ( *aBuf )->Des( ).MaxLength( ) < aSize )
    {
        delete *aBuf;
        
        *aBuf = NULL;
        *aBuf = HBufC::NewL( aSize );
    }
}

TChar fXmlParser::FindEscapeL( const TPtrC & aEscape )
{
    register TEscapedCharMapping *mapping = iEscapedCharMap;
    
    for( register TInt i = 0; i < iNumEscapeMappings; i++ )
    {
        if( mapping->iEscape.Compare( aEscape ) == 0 )
        {
            return mapping->iChar;
        }
        
        mapping++;
    }

    TChar re = 0;
    TUint16 ch = 0;
    
    if( aEscape.Left( 2 ).Compare( _L("#x") ) == 0 || aEscape.Left( 2 ).Compare( _L("%u") ) == 0 )
    {
        // %uXXXX : map to unicode(XXXX)
        TLex lex( aEscape.Mid( 2 ) );
        
        lex.Val( ch, EHex );
        re = ch;
    }
    else if( aEscape.Left( 1 ).Compare( _L("#") ) == 0 )
    {
        TLex lex( aEscape.Mid( 1 ) );
        
        lex.Val( ch, EDecimal );
        re = ch;
    }
    
    // escape not found!    modified by  2007..9.4 for chinese unicode char!
    //     _LIT(KNotSupportedEscape, "Not supported escape sequence &%S; on line %d!");
    //     iErrorReason.Format(KNotSupportedEscape, &aEscape, iLineNum);
    //     User::Leave(KErrParsingError);

    // to suppress compiler warning
    return re;
}

void fXmlParser::UnescapeL( const TPtrC & aFrom, TBool aTrim )
{
    TLex lex( aFrom );
    TChar esc_char;
    TInt offset;
    
    ValidateSizeL( &iUnescapeBuffer, aFrom.Length( ) );
    TPtr ptr = iUnescapeBuffer->Des( );
    
    ptr.Zero( );
    
    TBool parsing_escape = EFalse;
    
    while( !lex.Eos( ) )
    {
        TChar peek = lex.Peek( );
        
        switch( peek )
        {
            case KAmpersand:
                // consume ampersand and mark the start of escape
                lex.Inc( );
                lex.Mark( );
                parsing_escape = ETrue;
                break;
            case KSemicolon:
                if( parsing_escape )
                {
                    // consume semicolon and try to replace the escape sequence
                    offset = lex.Offset( );
                    esc_char = FindEscapeL( aFrom.Mid( lex.MarkedOffset( ), offset - lex.MarkedOffset( ) ) );
                    ptr.Append( esc_char );
                    parsing_escape = EFalse;
                }
                else
                {
                    // not parsing escape; append the semicolon normally
                    ptr.Append( KSemicolon );
                }
                lex.Inc( );
                break;
            default:
                // other char; consume and add to unescape buffer unless
                // currently parsing an escape sequence
                lex.Inc( );
                if( !parsing_escape )
                {
                    ptr.Append( peek );
                }
                break;
        }
    }
    
    if( aTrim )
    {
        iUnescapeBuffer->Des( ).Trim( );
    }
}

TBool fXmlParser::ParseL( const TDesC8 & aDocument )
{
    HBufC *buf = HBufC::NewLC( aDocument.Length( ) );
    
    buf->Des( ).Copy( aDocument );
    TBool ret = ParseL( *buf );
    
    CleanupStack::PopAndDestroy( buf );
    
    return ret;
}

TBool fXmlParser::ParseL( const TDesC & aDocument )
{
    iDocument.Set( aDocument );
    iLineNum = 1;
    TRAPD(err, ParseImplL());

    // after parsing is complete, make sure the buffers consume only the
    // default amount of memory
    AllocateDefaultSizeBuffersL( );
    
    if( err == KErrParsingError )
    {
        iListener.ParsingError( iErrorReason( ) );
        return EFalse;
    }
    else if( err != KErrNone )
    {
        User::Leave( err );
    }
    
    return ETrue;
}

void fXmlParser::ParseImplL( )
{
    TLex lex( iDocument );
    //TBool in_string = EFalse;
    TInt start_offs = 0;
    TPtrC ptr;
    
    iCoreState = ECoreNullState;
    
    // core parsing loop
    while( !lex.Eos( ) )
    {
        TChar peek = lex.Peek( );
        
        switch( peek )
        {
            case KLessThan:
                if( iCoreState == ECoreParsingElement )
                {
                    IllegalCharacterMetL( peek );
                }
                else if( iCoreState == ECoreParsingFreeText )
                {
                    ValidateSizeAndCopyL( &iFreeText, iDocument.Mid( lex.MarkedOffset( ), lex.Offset( ) - lex.MarkedOffset( ) ) );
                    ::XMLStringTrim( iFreeText->Des( ) );
                    
                    if( iFreeText->Length( ) > 0 )
                    {
                        UnescapeL( *iFreeText, ETrue );
                        iListener.FreeText( *iUnescapeBuffer );
                    }
                }
                
                lex.Mark( );
                iCoreState = ECoreParsingElement;
                break;
            case KGreaterThan:
                if( /*in_string ||*/( iCoreState == ECoreParsingFreeText ) )
                {
                    IllegalCharacterMetL( peek );
                }
                
                ProcessElementL( lex );
                
                // consume '>' and start parsing free text
                lex.Inc( );
                iCoreState = ECoreParsingFreeText;
                lex.Mark( );
                
                continue;
            case KLeftBracket:
                // consume the '['
                lex.Inc( );
                
                // compare the tag parsed so far to the CDATA start string
                start_offs = lex.MarkedOffset( ) + 1;
                ptr.Set( iDocument.Mid( start_offs, lex.Offset( ) - start_offs ) );
                
                if( ptr.Compare( KCDataSectionStart ) == 0 )
                {
                    // yes, the element is a CDATA section
                    ProcessCDATASectionL( lex );
                }
                continue;
            case KDash:
                // comsume the '-'
                lex.Inc( );
                
                // see if this is a comment
                start_offs = lex.MarkedOffset( ) + 1;
                ptr.Set( iDocument.Mid( start_offs, lex.Offset( ) - start_offs ) );
                
                if( ptr.Compare( KCommentSectionStart ) == 0 )
                {
                    // yes, the element is a comment section
                    ProcessCommentSectionL( lex );
                }
                continue;
#if 0
                case KDoubleQuote:
                case KApostrophe:
                in_string = !in_string;
                break;
#endif
            case KNewline:
                // increment line number and let control enter default branch
                iLineNum++;
            default:
                break;
        }
        
        // consume the character
        lex.Inc( );
    }
}

void fXmlParser::ProcessCommentSectionL( TLex & aLex )
{
    TInt start_offs = aLex.MarkedOffset( ) + 1 + KCommentSectionStart( ).Length( );
    TPtrC comment;
    
    // find the closing "-->"
    while( !aLex.Eos( ) )
    {
        if( aLex.Peek( ) == KGreaterThan )
        {
            if( iDocument.Mid( aLex.Offset( ) - 2, 2 ).Compare( KCommentSectionEnd ) == 0 )
            {
                comment.Set( iDocument.Mid( start_offs, aLex.Offset( ) - 2 - start_offs ) );
                
                iListener.Comment( comment );
                
                // consume the '>' and return
                aLex.Inc( );
                aLex.Mark( );
                iCoreState = ECoreParsingFreeText;
                return;
            }
        }
        
        // consume the character
        aLex.Inc( );
    }
}

void fXmlParser::ProcessCDATASectionL( TLex & aLex )
{
    TInt start_offs = aLex.MarkedOffset( ) + 1 + KCDataSectionStart( ).Length( );
    TPtrC sect;
    
    while( !aLex.Eos( ) )
    {
        if( aLex.Peek( ) == KGreaterThan )
        {
            if( iDocument.Mid( aLex.Offset( ) - 2, 2 ).Compare( KCDataSectionEnd ) == 0 )
            {
                // found the end of the CDATA section
                sect.Set( iDocument.Mid( start_offs, aLex.Offset( ) - 2 - start_offs ) );
                //RDebug::Print(_L("CDATA: %S"), &sect);

                iListener.FreeText( sect );
                
                // consume the '>' and return
                aLex.Inc( );
                aLex.Mark( );
                iCoreState = ECoreParsingFreeText;
                return;
            }
        }
        
        // consume the character
        aLex.Inc( );
    }
}

void fXmlParser::IllegalCharacterMetL( TChar aChar )
{
    _LIT(KIllegalChar, "Illegal character '%c' (%d) on line %d!");
    iErrorReason.Des( ).Format( KIllegalChar, ( TInt )aChar, ( TInt )aChar, iLineNum );
    User::Leave( KErrParsingError );
}

void fXmlParser::ProcessElementL( const TLex & aLex )
{
    TPtrC name, ns;
    
    TInt start_offs = aLex.MarkedOffset( ) + 1;
    TInt end_offs = aLex.Offset( );
    
    // extract the element tag
    TPtrC element = iDocument.Mid( start_offs, ( end_offs - start_offs ) );
    
    RAttributeList attr_list;
    
    // parse the element name/namespace
    TInt name_end_offs = 0;
    TPtrC elem_name_buf = FindElementNameL( element, name_end_offs );
    
    //TODO:: assert that name != "/" (case: </ a>)

    // if the element name starts with '?', then discard it
    if( elem_name_buf[ 0 ] == KQuestionMark )
    {
        return;
    }
    
    if( elem_name_buf[ 0 ] == KSlash )
    {
        // this is the end element (</element>)
        elem_name_buf.Set( elem_name_buf.Right( elem_name_buf.Length( ) - 1 ) );
        
        ParseNameL( elem_name_buf, name, ns );
        iListener.EndElement( name, ns );
    }
    else
    {
        // check if the name already contains the end element -marker '/'
        TBool end_element = EFalse;
        
        if( elem_name_buf[ elem_name_buf.Length( ) - 1 ] == KSlash )
        {
            elem_name_buf.Set( elem_name_buf.Left( elem_name_buf.Length( ) - 1 ) );
            end_element = ETrue;
        }

        // this is the start element (<element ...>)
        ParseNameL( elem_name_buf, name, ns );
        
        // locate && parse the attribute list
        if( element.Length( ) > name_end_offs )
        {
            ParseAttributeListL( element.Right( element.Length( ) - name_end_offs ), attr_list, end_element );
        }

        iListener.StartElement( name, ns, attr_list );
        
        // deallocate attribute list
        attr_list.ResetAndDestroy( );
        
        if( end_element )
        {
            // the element closes immediately, too (<element .... />)
            iListener.EndElement( name, ns );
        }
    }
}

TPtrC fXmlParser::FindElementNameL( const TPtrC & aElement, TInt & aEndOffset )
{
    TLex lex( aElement );
    TInt end_offs;
    
    // make sure there is a tag name in the first place
    if( lex.Peek( ) == KWhitespace )
    {
        _LIT(KNameNotFound, "Name not found in element %S on line %d!");
        iErrorReason.Des( ).Format( KNameNotFound, &aElement, iLineNum );
        User::Leave( KErrParsingError );
    }

    // by default, assume the tag name extends to the end of element
    end_offs = aElement.Length( );
    
    // find the extends of the element name
    while( !lex.Eos( ) )
    {
        TChar peek = lex.Peek( );
        
        switch( peek )
        {
            case KWhitespace:
            case KNewline:
            case KCarriageReturn:
                // found the end of the element name
                end_offs = lex.Offset( );
                
                // set lex to null string to stop parsing
                lex.Assign( KNullDesC );
                break;
            case KEquals:
            case KLessThan:
            case KGreaterThan:
                // illegal character met!
                IllegalCharacterMetL( peek );
                break;
            default:
                // ok character - skip to the next whitespace or end of string
                lex.SkipCharacters( );
                continue;
        }
        
        // consume the character
        lex.Inc( );
    }

    aEndOffset = end_offs;
    
    return aElement.Left( end_offs );
}

void fXmlParser::ParseNameL( const TPtrC & aNameBuf, TPtrC & aName, TPtrC & aNamespace )
{
    TInt sep_offs = aNameBuf.Locate( KColon );
    
    if( sep_offs == KErrNotFound )
    {
        // namespace not defined (form: <name>)
        aNamespace.Set( KNullDesC );
        aName.Set( aNameBuf );
    }
    else
    {
        // namespace defined (form: <ns:name>) - validate both, name and namespace
        if( ( sep_offs == 0 ) || ( sep_offs == ( aNameBuf.Length( ) - 1 ) ) )
        {
            _LIT(KBadElementName, "Bad element name '%S' on line %d!");
            iErrorReason.Des( ).Format( KBadElementName, &aNameBuf, iLineNum );
            User::Leave( KErrParsingError );
        }

        aNamespace.Set( aNameBuf.Left( sep_offs ) );
        aName.Set( aNameBuf.Right( aNameBuf.Length( ) - sep_offs - 1 ) );
    }
}

void fXmlParser::ParseAttributeListL( const TPtrC & aAttrBuf, RAttributeList & aAttrList, TBool & aEndElement )
{
    // _LIT(KBadSyntax, "Bad syntax on line %d!");

    aEndElement = EFalse;
    TLex lex( aAttrBuf );
    TAttrParserState state = EAttrNullState;
    TPtrC ptr;
    TPtrC name, ns;
    
    while( !lex.Eos( ) )
    {
        TChar peek = lex.Peek( );
        
        //RDebug::Print(_L("PEEK: %c"), (TInt)peek);

        switch( peek )
        {
            case KTab:
            case KWhitespace:
            case KCarriageReturn:
            case KNewline:
                if( state == EAttrParsingAttrName )
                {
                    //                     IllegalCharacterMetL(peek);
                    //                     return;
                    lex.SkipSpace( );
                }
                else if( state != EAttrParsingAttrValue )
                {
                    lex.SkipSpace( );
                    state = EAttrNullState;
                }
                else
                {
                    lex.Inc( );
                }
                continue;
            case KEquals:
                // check state
                if( ( state == EAttrNullState ) )
                {
                    IllegalCharacterMetL( peek );
                    return;
                }
                
                if( state == EAttrParsingAttrValue )
                {
                    lex.Inc( );
                    break;
                }
                
                // end parsing attribute name
                ptr.Set( aAttrBuf.Mid( lex.MarkedOffset( ), lex.Offset( ) - lex.MarkedOffset( ) ) );
                ValidateSizeAndCopyL( &iAttrName, ptr );
                
                // consume and peek for '"'
                lex.Inc( );
                if( lex.Peek( ) == KWhitespace )
                    lex.SkipSpace( );
                if( lex.Peek( ) != KDoubleQuote && lex.Peek( ) != KApostrophe )
                {
                    IllegalCharacterMetL( lex.Peek( ) );
                    return;
                }
                else
                {
                    // consume '"'
                    lex.Inc( );
                    lex.Mark( );
                    state = EAttrParsingAttrValue;
                }
                break;
            case KDoubleQuote:
            case KApostrophe:
                // check state
                if( ( state == EAttrNullState ) || ( state == EAttrParsingAttrName ) )
                {
                    IllegalCharacterMetL( peek );
                    return;
                }
                
                // end parsing attr value - add new entry
                ptr.Set( aAttrBuf.Mid( lex.MarkedOffset( ), lex.Offset( ) - lex.MarkedOffset( ) ) );
                UnescapeL( ptr );
                ParseNameL( *iAttrName, name, ns );
                aAttrList.Append( CXmlAttribute::NewL( name, ns, *iUnescapeBuffer ) );
                lex.Inc( );
                state = EAttrNullState;
                break;
            case KSlash:
                if( state == EAttrNullState )
                {
                    // end element -marker met
                    aEndElement = ETrue;
                    return;
                }
                // otherwise allow control proceed to default branch
            default:
                if( state == EAttrNullState )
                {
                    // start parsing attribute name
                    state = EAttrParsingAttrName;
                    lex.Mark( );
                }
                
                // consume character
                lex.Inc( );
                break;
        }
    }
}

